#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "mpi.h"
#include "MatrixVectorRead.h"

#define MATRIX_TAG  123;
#define MATRIX_INT_TAG 234;
#define VECTOR_TAG 345;
#define VECTOR_INT_TAG 456;

#define MATRIX_ROW_TAG 567;
#define MATRIX_COL_TAG 678;
#define VECTOR_ROW_TAG 789;
#define VECTOR_COL_TAG 890;

/*
typedef struct {
	int row;
	int col;
	double value;
} MatrixCell;*/

static int matrixSize, vectorSize;

static int matrixRow, matrixCol;
static int vectorRow, vectorCol;

static MatrixCell *matrix;
static MatrixCell *vector;

int getMatrixSize() {
	return matrixSize;
}

int getMatrixRowSize() {
	return matrixRow;
}

int getMatrixColumnSize() {
	return matrixCol;
}

MatrixCell* getMatrix() {
	return matrix;
}

int getVectorSize() {
	return vectorSize;
}

int getVectorRowSize() {
	return vectorRow;
}

int getVectorColumnSize() {
	return vectorCol;
}

MatrixCell* getVector() {
	return vector;
}

void writeOutputVector(char* filename, MatrixCell* mat, int row, int col, int size) {
	int i = 0;
	FILE *fp = fopen(filename, "w");
	if(fp == NULL) {
		printf("File Not Opened");
		return;
	}

	fprintf(fp,"%%%%MatrixMarket matrix coordinate real general\n");

	fprintf(fp, "\t%d\t%d\t%d\n",row, col, size);

	for(i = 0; i < size; ++i) {
		fprintf(fp, "\t%d\t%d\t%lf\n",mat[i].row, mat[i].col, mat[i].value);
	}

	fclose(fp);
}

void _getVectorFromRoot(int my_rank) {
	int tag;
	MPI_Status status;

	/* MPI_Aint type used to be consistent with syntax of */
	/* MPI_Type_extent routine */
	MPI_Aint offsets[2], extent;

	MPI_Datatype MatrixCellType, oldtypes[2];
	int blockcounts[2];

	/*
	 typedef struct _entry {
	 int row;
	 int col;
	 double value;
	 } MatrixCell;*/

	/* Setup description of the 2 MPI_INT fields row and col */
	offsets[0] = 0;
	oldtypes[0] = MPI_INT;
	blockcounts[0] = 2;

	/* Setup description of the 1 MPI_DOUBLE field value */
	/* Need to first figure offset by getting size of MPI_INT */
	MPI_Type_extent(MPI_INT, &extent);
	offsets[1] = 2 * extent;
	oldtypes[1] = MPI_DOUBLE;
	blockcounts[1] = 1;

	/* Now define structured type and commit it */

	MPI_Type_struct(2, blockcounts, offsets, oldtypes, &MatrixCellType);
	MPI_Type_commit(&MatrixCellType);

	tag = VECTOR_INT_TAG;
	MPI_Recv(&vectorSize, 1, MPI_INT, 0, tag, MPI_COMM_WORLD, &status);

	vector = (MatrixCell*) malloc(vectorSize * sizeof(MatrixCell));

	tag = VECTOR_TAG;
	MPI_Recv(vector, vectorSize, MatrixCellType, 0, tag, MPI_COMM_WORLD,
			&status);

	tag = VECTOR_ROW_TAG;
	MPI_Recv(&vectorRow, 1, MPI_INT, 0, tag, MPI_COMM_WORLD, &status);

	tag = VECTOR_COL_TAG;
	MPI_Recv(&vectorCol, 1, MPI_INT, 0, tag, MPI_COMM_WORLD, &status);

}

void _sendVectorToProcess(MatrixCell *sendVector, int procSize, int send_rank,
		int my_rank) {
	/* MPI_Aint type used to be consistent with syntax of */
	/* MPI_Type_extent routine */

	int thisTag;

	MPI_Aint offsets[2], extent;
	MPI_Datatype MatrixCellType, oldtypes[2];
	int blockcounts[2];

	/*
	 typedef struct _entry {
	 int row;
	 int col;
	 double value;
	 } MatrixCell;*/

	/* Setup description of the 2 MPI_INT fields row and col */
	offsets[0] = 0;
	oldtypes[0] = MPI_INT;
	blockcounts[0] = 2;

	/* Setup description of the 1 MPI_DOUBLE field value */
	/* Need to first figure offset by getting size of MPI_INT */
	MPI_Type_extent(MPI_INT, &extent);
	offsets[1] = 2 * extent;
	oldtypes[1] = MPI_DOUBLE;
	blockcounts[1] = 1;

	/* Now define structured type and commit it */

	MPI_Type_struct(2, blockcounts, offsets, oldtypes, &MatrixCellType);
	MPI_Type_commit(&MatrixCellType);

	thisTag = VECTOR_INT_TAG;
	MPI_Send(&procSize, 1, MPI_INT, send_rank, thisTag, MPI_COMM_WORLD);

	thisTag = VECTOR_TAG;
	MPI_Send(sendVector, procSize, MatrixCellType, send_rank, thisTag,
			MPI_COMM_WORLD);

	thisTag = VECTOR_ROW_TAG;
	MPI_Send(&vectorRow, 1, MPI_INT, send_rank, thisTag, MPI_COMM_WORLD);

	thisTag = VECTOR_COL_TAG;
	MPI_Send(&vectorCol, 1, MPI_INT, send_rank, thisTag, MPI_COMM_WORLD);
}

void _readVector(char* vectorFile, int my_rank, int numProcess) {
	/* Temporary Variables */
	char c;
	int i;
	int k;
	int tempRow, tempCol;
	double tempValue;

	FILE* fp;
	int* procStart = (int*) malloc(numProcess * sizeof(int));
	int* procEnd = (int*) malloc(numProcess * sizeof(int));

	fp = fopen(vectorFile, "r");
	/* read all the first line characters */
	while (1) {
		c = fgetc(fp);
		if (c == '\n')
			break;
		if (feof(fp))
			return;
	}

	fscanf(fp, "%d", &vectorRow);
	fscanf(fp, "%d", &vectorCol);
	fscanf(fp, "%d", &vectorSize);

	/* Set the start and end matrix for each */
	procStart[0] = 0;
	for (i = 1; i < numProcess; ++i)
		procStart[i] = i * (vectorSize / numProcess);

	for (i = 0; i < (numProcess - 1); ++i)
		procEnd[i] = (i + 1) * (vectorSize / numProcess);
	procEnd[numProcess - 1] = vectorSize;
	/* Here is the end of reading data */

	for (i = 0; i < numProcess; ++i) {
		int procSize = procEnd[i] - procStart[i];
		MatrixCell* sendVector = (MatrixCell*) malloc(
				sizeof(MatrixCell) * procSize);

		for (k = 0; k < procSize; ++k) {
			fscanf(fp, "%d", &tempRow);
			fscanf(fp, "%d", &tempCol);
			fscanf(fp, "%lf", &tempValue);
			sendVector[k].row = tempRow;
			sendVector[k].col = tempCol;
			sendVector[k].value = tempValue;
		}

		/* Just for testing purpose */
		/*
		for (k = 0; k < procSize; ++k) {
			printf("\t%d\t%d\t%lf\n", sendVector[k].row, sendVector[k].col,
					sendVector[k].value);
		}*/

		/* send procSize and sendMatrix to process with id i */
		if (i != 0)
			_sendVectorToProcess(sendVector, procSize, i, my_rank);

		/* free the sendMatrix out Here */
		if (i != 0)
			free(sendVector);
		else {
			vectorSize = procSize;
			vector = sendVector;
		}
	}

	fclose(fp);
}

void _getMatrixFromRoot(int my_rank) {
	int tag;
	MPI_Status status;

	/* MPI_Aint type used to be consistent with syntax of */
	/* MPI_Type_extent routine */
	MPI_Aint offsets[2], extent;

	MPI_Datatype MatrixCellType, oldtypes[2];
	int blockcounts[2];

	/*
	 typedef struct _entry {
	 int row;
	 int col;
	 double value;
	 } MatrixCell;*/

	/* Setup description of the 2 MPI_INT fields row and col */
	offsets[0] = 0;
	oldtypes[0] = MPI_INT;
	blockcounts[0] = 2;

	/* Setup description of the 1 MPI_DOUBLE field value */
	/* Need to first figure offset by getting size of MPI_INT */
	MPI_Type_extent(MPI_INT, &extent);
	offsets[1] = 2 * extent;
	oldtypes[1] = MPI_DOUBLE;
	blockcounts[1] = 1;

	/* Now define structured type and commit it */

	MPI_Type_struct(2, blockcounts, offsets, oldtypes, &MatrixCellType);
	MPI_Type_commit(&MatrixCellType);

	tag = MATRIX_INT_TAG;
	MPI_Recv(&matrixSize, 1, MPI_INT, 0, tag, MPI_COMM_WORLD, &status);

	matrix = (MatrixCell*) malloc(matrixSize * sizeof(MatrixCell));

	tag = MATRIX_TAG;
	MPI_Recv(matrix, matrixSize, MatrixCellType, 0, tag, MPI_COMM_WORLD,
			&status);

	tag = MATRIX_ROW_TAG;
	MPI_Recv(&matrixRow, 1, MPI_INT, 0, tag, MPI_COMM_WORLD, &status);

	tag = MATRIX_COL_TAG;
	MPI_Recv(&matrixCol, 1, MPI_INT, 0, tag, MPI_COMM_WORLD, &status);

}

void _sendMatrixToProcess(MatrixCell *sendMatrix, int procSize, int send_rank,
		int my_rank) {
	/* MPI_Aint type used to be consistent with syntax of */
	/* MPI_Type_extent routine */

	int thisTag;

	MPI_Aint offsets[2], extent;
	MPI_Datatype MatrixCellType, oldtypes[2];
	int blockcounts[2];

	/*
	 typedef struct _entry {
	 int row;
	 int col;
	 double value;
	 } MatrixCell;*/

	/* Setup description of the 2 MPI_INT fields row and col */
	offsets[0] = 0;
	oldtypes[0] = MPI_INT;
	blockcounts[0] = 2;

	/* Setup description of the 1 MPI_DOUBLE field value */
	/* Need to first figure offset by getting size of MPI_INT */
	MPI_Type_extent(MPI_INT, &extent);
	offsets[1] = 2 * extent;
	oldtypes[1] = MPI_DOUBLE;
	blockcounts[1] = 1;

	/* Now define structured type and commit it */

	MPI_Type_struct(2, blockcounts, offsets, oldtypes, &MatrixCellType);
	MPI_Type_commit(&MatrixCellType);

	thisTag = MATRIX_INT_TAG;
	MPI_Send(&procSize, 1, MPI_INT, send_rank, thisTag, MPI_COMM_WORLD);

	thisTag = MATRIX_TAG;
	MPI_Send(sendMatrix, procSize, MatrixCellType, send_rank, thisTag,
			MPI_COMM_WORLD);

	thisTag = MATRIX_ROW_TAG;
	MPI_Send(&matrixRow, 1, MPI_INT, send_rank, thisTag, MPI_COMM_WORLD);

	thisTag = MATRIX_COL_TAG;
	MPI_Send(&matrixCol, 1, MPI_INT, send_rank, thisTag, MPI_COMM_WORLD);

}

void _readMatrix(char* matrixFile, int my_rank, int numProcess) {
	/* Temporary Variables */
	char c;
	int i;
	int k;
	int tempRow, tempCol;
	double tempValue;

	FILE* fp;
	int* procStart = (int*) malloc(numProcess * sizeof(int));
	int* procEnd = (int*) malloc(numProcess * sizeof(int));

	fp = fopen(matrixFile, "r");
	/* read all the first line characters */
	while (1) {
		c = fgetc(fp);
		if (c == '\n')
			break;
		if (feof(fp))
			return;
	}

	fscanf(fp, "%d", &matrixRow);
	fscanf(fp, "%d", &matrixCol);
	fscanf(fp, "%d", &matrixSize);

	/* Set the start and end matrix for each */
	procStart[0] = 0;
	for (i = 1; i < numProcess; ++i)
		procStart[i] = i * (matrixSize / numProcess);

	for (i = 0; i < (numProcess - 1); ++i)
		procEnd[i] = (i + 1) * (matrixSize / numProcess);
	procEnd[numProcess - 1] = matrixSize;
	/* Here is the end of reading data */

	for (i = 0; i < numProcess; ++i) {
		int procSize = procEnd[i] - procStart[i];
		MatrixCell* sendMatrix = (MatrixCell*) malloc(
				sizeof(MatrixCell) * procSize);

		for (k = 0; k < procSize; ++k) {
			fscanf(fp, "%d", &tempRow);
			fscanf(fp, "%d", &tempCol);
			fscanf(fp, "%lf", &tempValue);
			sendMatrix[k].row = tempRow;
			sendMatrix[k].col = tempCol;
			sendMatrix[k].value = tempValue;
		}

		/* Just for testing purpose */
		/*
		for (k = 0; k < procSize; ++k) {
			printf("\t%d\t%d\t%lf\n", sendMatrix[k].row, sendMatrix[k].col,
					sendMatrix[k].value);
		}*/

		/* send procSize and sendMatrix to process with id i */
		if (i != 0)
			_sendMatrixToProcess(sendMatrix, procSize, i, my_rank);

		/* free the sendMatrix out Here */
		if (i != 0)
			free(sendMatrix);
		else {
			matrixSize = procSize;
			matrix = sendMatrix;
		}
	}

	fclose(fp);
}

void _readMatrixVector(char* matrixFile, char* vectorFile, int my_rank,
		int numProcess) {
	_readMatrix(matrixFile, my_rank, numProcess);
	_readVector(vectorFile, my_rank, numProcess);
	/*
	printf("Matrix File: %s;\tVector File: %s\n;\trank: %d\tnumProcess: %d\n",
			matrixFile, vectorFile, my_rank, numProcess);
	*/
}

void _getMatrixVector(int my_rank) {
	_getMatrixFromRoot(my_rank);
	_getVectorFromRoot(my_rank);
}

void readMatrixVector(char* matrixFile, char* vectorFile, int my_rank,
		int numProcess) {
	FILE* fp = fopen(matrixFile, "r");
	if(fp == NULL) {
		printf("Matrix File doesnot exist\n");
		return;
	}
	fclose(fp);
	fp = fopen(vectorFile, "r");
	if(fp == NULL) {
		printf("Vector File doesnot exist\n");
		return;
	}
	fclose(fp);
		
	if (my_rank == 0)
		_readMatrixVector(matrixFile, vectorFile, my_rank, numProcess);
	else
		_getMatrixVector(my_rank);
}
